import os
import logging, logging.config
scriptpath = os.path.dirname(os.path.realpath(__file__))
logging.config.fileConfig(scriptpath+'/logging.conf')
logger = logging.getLogger('simulation_root')

import absorb_photons
import fact_spline_interpolations as fact_si
from Turnable import *
from detektorsim import FasterPulse, measureIntegral
import detektorsim

class Mirror( Turnable ):
    """ Mirror description/abstraction class
        
        from Turnable:
            turn( axis, angle) - turn the mirror around *axis* by *angle*
        
        *pos* - the position of the center of the mirror plane
        
        *turnables*:
            *pos* 
            *dir*

    """
    def __init__(self, index, pos, normal_vector, focal_length, hex_size ):
        self.inner_circle_radius = hex_size/2.
        self.outer_circle_radius = hex_size/np.sqrt(3)
        
        self.pos = pos
        self.dir = normal_vector
        self.y_vector = np.array([0., self.outer_circle_radius ,0.])
        self.x_vector = np.array([self.inner_circle_radius, 0., 0.])
        
        # list for Turnable.turn()
        self._turnables = [ "pos", "dir", "x_vector", "y_vector"]
        
        self.index = index
        self.focal_length = focal_length
        
        
        
    
    # standard __repr__
    def __repr__( self ):
        return "%s(%r)" % (self.__class__, self.__dict__)

class Photon( Turnable ):
    """ Photon description/abstraction class
        
        from Turnable:
            turn( axis, angle) - turn the mirror around *axis* by *angle*
        
        *pos* - the position of the photon in 3D
        *dir* - the direction of flight in 3D
        *time*- time since 2st interaction [ns]
        *wavelength* - wavelength in [nm]
        *mother* - particle ID of mother particle
        
        *turnables*:
            *pos* 
            *dir*
        
    """
    def __init__(self, photon_definition_array ):
        """ Construct Photon form 10-element 1D-np.array
        
        the photon constructor understands the 10-element 1D-np.array
        which is stored inside a run.event.photons 2D-np.array

        the *photon_definition_array* pda contains:
            pda[0] - encoded info
            pda[1:3] - x,y position in cm
            pda[3:5] - u,v cosines to x,y axis  --> so called direction cosines
            pda[5] - time since first interaction [ns]
            pda[6] - height of production in cm
            pda[7] - j ??
            pda[8] - imov ??
            pda[9] - wavelength [nm]
        """
        pda = photon_definition_array
        
        self.pos = np.array([pda[1],pda[2],0.])
        self.dir = np.array([pda[3],pda[4], np.sqrt(1-pda[3]**2-pda[4]**2) ])
        self._turnables = ("pos", "dir")
        
        self.wavelength = pda[9]
        self.time = pda[5]
    
    # standard __repr__
    def __repr__( self ):
        return "%s(%r)" % (self.__class__, self.__dict__)


class Focal_Plane( Turnable ):
    """
    """
    def __init__(self, pos, dir, size ):
        self.pos = pos
        self.dir = dir
        self.size = size
        self._turnables = ["pos", "dir"]
        
class Point( Turnable ):
    """
    """
    def __init__(self, pos):
        self.pos = pos
        self._turnables = ["pos"]
    





class IsInInnerCircle:
    """ Provide function like object for deletion of photons outside a circle.
    
    In order to use _absorb_photons() one need a function, that provides a survival probability.
    An object of this class (once instanciated with a radius) can be called like a function.
    """
    
    def __init__(self, radius=210):
        """ Instanciate IsInInnerCircle with a radius
        
        :param radius: radius of the circle
        :type  radius: float
        """
        self.radius = radius

    def __call__(self,pos):
        """ Return numpy array full of probabilities. 0. when *pos* outside radius.
        1. when *pos* is inside. That' all.

        :param pos: position(s) of the photon(s)
        :type  pos: ndarray
        """
        radius = self.radius
        logger.debug("isInsideCircle started up")
        logger.debug("   pos.shape %r" % repr(pos.shape))
        distance = np.sqrt((pos**2).sum(axis=1))
        logger.debug("   distance.shape %r" % distance.shape)
        result = (distance<radius).astype(int)
        logger.debug("   result.shape %r" % result.shape)
        return result


def _read_reflector_definition_file( filename ):
    """
    """
    mirrors = []
    
    f = open( filename )
    for index, line in enumerate(f):
        if line[0] == '#':
            continue
        line = line.split()
        if len(line) < 8:
            continue
        #print line
        
        # first 3 colums in the file are x,y,z coordinates of the center
        # of this mirror in cm, I guess
        pos = np.array(map(float, line[0:3]))
        
        # the next 3 elements are the elements of the normal vector
        # should be normalized already, so the unit is of no importance.
        normal_vector = np.array(map(float,line[3:6]))
        
        # focal length of this mirror in mm
        focal_length = float(line[6])
        
        # size of the hexagonal shaped facette mirror, measured as the radius
        # of the hexagons *inner* circle.
        hex_size = float(line[8])
        mirror = Mirror( index, pos, normal_vector, focal_length, hex_size )
        
        mirrors.append(mirror)
        
    return mirrors
    
def put_settings_into_dict( thedict ):
    
    settingsdictname = 'settings'
    
    thedict[settingsdictname] = {}
    
    thedict[settingsdictname]['run_number'] = 0
    thedict[settingsdictname]['run_type_string'] = 'simulation run'
    thedict[settingsdictname]['randomSeed'] = 0
    
    Wavelength_dependency_description_files = [ 
    "../tel_definitions/Transmittance_1439Cones_FACT_bearbeitet.txt",
    "../tel_definitions/fact-pde-1.4V.txt",
    "../tel_definitions/MirrorReflectivity_Lustermann_FACT_bearbeitet.txt" ]
    
    angular_acceptance_files = [
    "../tel_definitions/fact-cones-angular-acceptance.txt" ]
    
    
    
    spw = fact_si.interpolate_from_files( 
                Wavelength_dependency_description_files )
    thedict[settingsdictname]['survival_prob_wavelength'] = spw
    
    thedict[settingsdictname]['is_in_inner_circle_checker'] = IsInInnerCircle(210)
    
    

    #~ So in order to simulate reflections on mirrors and find out, if photons have hit the focal plane
    #~ we need to know where all the stuff is in the world. So we need coordinates and stuff like that.
    #~ All this is stored inside the thedict-dict so following functions can access that information.
    #~ 
    #~ The following new keys are added:
    #~ 
    #~ * thedict['mirrors'] --> list of reflector.Mirror objects from ../tel_definitions/fact-reflector.txt
    #~ * thedict['mirror_alignment_point'] --> reflector.Point object::
    #~      Point(  pos=np.array([0.,0.,978.132]) )
    #~ * thedict['focal_plane'] --> reflector.Focal_Plane object
    #~ 
    #~ this is how it was created::
    #~      focal_plane = Focal_Plane(  pos=np.array([0.,0.,978.132/2.]),   # center of focal_plane
    #~                                  dir=np.array([0., 0., 1.]),         # direction
    #~                                  size=40 )                           # radius in cm 
    #~ 
    #~ This is a point, where self written classes are used massively. For no good reason,
    #~ so in the near future, this will change, I guess.
    #~ 
    #~ The magic number 978.132 I found written in a comment in fact-reflector.txt and there it
    #~ described the point in twice the focal distance from the reflector center. So for this simulation
    #~ I assumed the focal plane should be in one half of this distance. After that we can find the intersection points 
    #~ of the reflected photons with the *focal_plane* like this:
    
    mirror_file_name = "../tel_definitions/fact-reflector.txt"
    mirrors = _read_reflector_definition_file( mirror_file_name )
    thedict[settingsdictname]['mirrors'] = mirrors
    
    mirror_alignment_point = Point(  pos=np.array([0.,0.,978.132]) )
    thedict[settingsdictname]['mirror_alignment_point'] = mirror_alignment_point


    focal_plane = Focal_Plane(  pos=np.array([0.,0.,978.132/2.]),   # center of focal_plane
                                dir=np.array([0., 0., 1.]),         # direction
                                size=25 )                           # radius in cm 
    thedict[settingsdictname]['focal_plane'] = focal_plane
    
    thedict[settingsdictname]['PhotonNoiseInfo'] = {}
    thedict[settingsdictname]['PhotonNoiseInfo']['NsbRate'] = 40. * 1e6     # Hz
    thedict[settingsdictname]['PhotonNoiseInfo']['DcRate'] = 4. * 1e6       # Hz
    thedict[settingsdictname]['SimInfo'] = {}
    thedict[settingsdictname]['SimInfo']['StartTimeOffset'] = -22 * 1e-9    # sec
    thedict[settingsdictname]['SimInfo']['SimulatedTime'] = 150 * 1e-9      # sec
    thedict[settingsdictname]['SimInfo']['SamplingFreqency'] = 2. * 1e9     # Hz
    SimulatedTime = thedict[settingsdictname]['SimInfo']['SimulatedTime']
    SamplingFreqency = thedict[settingsdictname]['SimInfo']['SamplingFreqency']
    NumberOfSamples = int(SimulatedTime*SamplingFreqency)
    thedict[settingsdictname]['SimInfo']['NumberOfSamples'] = NumberOfSamples # This is the ROI
    
    AdcDict = 'AdcDict'
    thedict[settingsdictname][AdcDict] = {}
    #~ thedict[settingsdictname][AdcDict]['AdcFullScale'] = 200.      # Photons
    #~ thedict[settingsdictname][AdcDict]['AdcOffset'] = 10.          # Photons
    #~ thedict[settingsdictname][AdcDict]['AdcResolution'] = 4096.    # LSBs
    #~ 
    thedict[settingsdictname][AdcDict]['AdcFullScale'] = 2000.      # mV
    thedict[settingsdictname][AdcDict]['AdcOffset'] = 0.            # mV
    thedict[settingsdictname][AdcDict]['AdcResolution'] = 4096.     # LSBs
    
    #thedict[settingsdictname][AdcDict]['AnotherOffset'] = -2048    # LSBs
    
    thedict[settingsdictname][AdcDict]['NoiseLevel'] = 2            # Electronics noise level in units of mV
    
    # Due to optical crosstalk the expectation value of the Integral
    # of a single photon pulse is not incredibly well defined. Sometimes 
    # one gets a single photon, sometimes a double, sometzimes more...
    # measureIntegral() really evaluates the integral for a certain number of pulses
    # so the result should be pretty reliable....
    thedict[settingsdictname]['SipmInfo'] = {}
    sipminfo = thedict[settingsdictname]['SipmInfo']
    
    
    
    
    
    sipminfo['crosstalk_probability'] = 0.1 
    sipminfo['pulse_tau1'] = 5.004/2. * 1e-9
    sipminfo['pulse_tau2'] = 31.245/2. * 1e-9
    sipminfo['pulse_max_time_resolution'] = 2.e-11
    sipminfo['pulse_sampling_period'] = 5.e-10 # this is exactly 1./2Ghz but I need the period there :-|
    
    sipminfo['standard_pulse_function'] =  detektorsim.FasterPulse(thedict)
    sipminfo['PulseIntegral'] = detektorsim.measureIntegral(thedict)
    sipminfo['AmplitudeGenerationFunction'] = detektorsim.calculateAmplitudeWithCrosstalk
    sipminfo['MeanAmplitude'] = detektorsim.calculateAmplitudeWithCrosstalk(thedict, size=10000).mean()
    
   
    return thedict
